/*  
 *  Copyright 2006 Paul Jack.
 *
 *  This file is part of the Dex compiler suite. 
 *  
 *  Dex is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *  
 *  Dex is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *  Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package dex.compiler.interpreter;


import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import dex.compiler.checker.type.Type;
import dex.compiler.checker.type.TypeManager;
import dex.compiler.interpreter.value.Defaults;
import dex.compiler.interpreter.value.Value;
import dex.compiler.model.definition.TypedName;
import dex.compiler.model.definition.Unit;
import dex.compiler.model.program.Program;


/**
 * A program that initializes and remembers global variable values
 * for loaded units.
 */
class InterpretedProgram implements Program {


	/**
	 * The delegate program that actually loads units.
	 */
	final private Program program;


	/**
	 * Maps fully-qualified global variable name to the value
	 * for that variable.
	 */
	final private Map<String,Value> globals;
	
	
	/**
	 * Used to come up with default values for global variables.
	 */
	private TypeManager manager;


	/**
	 * Set of unit names that have already been loaded.
	 * Used to prevent re-initialization of variables.
	 */
	final private Set<String> loadedUnits;
	

	/**
	 * Constructor.  Note that InterpretedPrograms are not ready for use 
	 * until {@link #setTypeManager(TypeManager)} has been invoked.
	 * 
	 * @param program  the delegate program
	 * @param globals  maps fully-qualified global variable names to their values
	 * @see #setTypeManager(TypeManager)
	 */
	public InterpretedProgram(Program program, Map<String,Value> globals) {
		this.program = program;
		this.globals = globals;
		this.loadedUnits = new HashSet<String>();
	}
	

	/**
	 * Sets the type manager for this program.  A type manager must be
	 * set before any units are loaded.
	 * 
	 * <p>This method exists to resolve a nasty circular dependency.
	 * An InterpretedProgram needs a type manager to come up with default
	 * values for newly loaded global variables.  However, type managers
	 * must be constructed with a program instance.
	 * 
	 * <p>So, to get a functional InterpretedProgram, you must:
	 * 
	 * <pre>
	 * InterpretedProgram p = new InterpretedProgram(program, globals);
	 * TypeManager tm = new TypeManager(p, reporter);
	 * p.setTypeManager(tm);
	 * </pre>
	 *
	 * @param manager  the manager to set
	 * @throws IllegalStateException  if a manager has already been set
	 * @throws IllegalArgumentException if the given manager was not
	 *   constructed using this program
	 */
	public void setTypeManager(TypeManager manager) {
		if (this.manager != null) {
			throw new IllegalStateException();
		}
		if (manager.getProgram() != this) {
			throw new IllegalArgumentException();
		}
		this.manager = manager;
	}


	public Unit loadComplete(String unitName) {
		Unit result = program.loadComplete(unitName);
		if (!loadedUnits.add(unitName)) {
			return result;
		}
		for (TypedName global: result.getVariables()) {
			Type type = manager.toType(result, global.getType());
			globals.put(unitName + ':' + global.getName(), Defaults.get(type));
		}
		return result;
	}


	public Unit loadProfile(String unitName) {
		return loadComplete(unitName);
	}


	public boolean unitExists(String unitName) {
		return program.unitExists(unitName);
	}

}
